Okapi BM25 是經典資訊檢索模型之一,也是 Elasticsearch Elasticsearch similarity 計算檢索相關性分數的預設演算法。BM 是 Best Matching 的縮寫,25 是其實驗參數配置所實驗的次數。
BM25 的模型算式如下
其中 query Q 包含 terms
此算式中包含六個部分:
shane connelly
則 shane
、 connelly
。
- docCount 是文件總數,在 Elasticsearch 中預設會是看所處的 Elasticsearch primary shard 中的文件,若透過
search_type=dfs_query_then_fetch
則會跨 shards 計算。是包含 term 的文件數量。 - 舉例來說,query Q =
shane connelly
,= shane
出現全部 4 個文件中,則 IDF("shane") =, = connelly
只出現在四個文件中的兩個文件中,則 IDF(connelly
) =- IDF 代表著每個 term 的是否常見,若是常見的 term 如英文中的
the
,a
,an
等詞相較其他比較不常見的詞會貢獻較少的分數,例如 querythe president
則the
所貢獻的分數較低、president
貢獻分數較高,意味著president
這個 term 的重要性高過the
在檢索時應著重考慮。
分母中出現的
參數 b 用於調整
參數
由於 Elasticsearch 預設會使用 5 個 Elasticsearch primary shard 作為一個 Elasticsearch index,每一個 shard 未必會收到所有的文件,因此在使用 BM25 作為計算的 相關性分數 時會受到其所考慮的文件並非是全局的而受到偏誤,例如在 shard 1 的文件較多則 IDF 所計算的分數可能就較其他文件較少的 shard 分數較低而誤判排序結果。
解決的方法有三:
放置更多的文件,透過文件數量的增加,每個 shard 中的 term 相關統計都會標準化,每個 shard 就會收到更多的文件,這樣就可以減少排序的誤判。
設定 number_of_shards
為 1,降低 shard 的數量,集中文件的放置位置,進而讓統計結果不會有偏誤。
在搜尋 request 加入 ?search_type=dfs_query_then_fetch
,這會首先搜集所有分散文件的詞頻(Distributed term frequency, DFS = Distributed frequency search),此結果會和設定只有一個 shard 的回傳 相關性分數 結果一樣,然而差別在於額外的往返在速度重要過搜尋結果的情境下是不必要的,另外在文件數量增加時這麼做會搜尋排序並不會有所提升反而犧牲效能。
在進行調整
ny
換轉換為搜尋 (ny OR ("new york"))
,conjunction 的邏輯也是可行的只需要在 query 中加入 "auto_generate_synonyms_phrase_query" : false
,例如 ny city
query 會被轉換為搜尋 (ny OR (new AND york)) city
搜尋。若這些都調教過,調整
在 Elasticsearch 中提供了 Explain API 解釋為什麼搜尋結果的給分是怎麼給的,只需要在一般的 query endpoint 後再加入 _explain
即可,例如
GET /people3/_doc/4/_explain
{
"query": {
"match": {
"title": "shane connelly"
}
}
}
會得到如下結果:
{
"_index": "people3",
"_type": "_doc",
"_id": "4",
"matched": true,
"explanation": {
"value": 0.71437943,
"description": "sum of:",
"details": [
{
"value": 0.102611035,
"description": "weight(title:shane in 3) [PerFieldSimilarity], result of:",
"details": [
{
"value": 0.102611035,
"description": "score(doc=3,freq=1.0 = termFreq=1.0\n), product of:",
"details": [
{
"value": 0.074107975,
"description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
"details": [
{
"value": 6,
"description": "docFreq",
"details": []
}, {
"value": 6,
"description": "docCount",
"details": []
}
]
},
{
"value": 1.3846153,
"description": "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:",
"details": [
{
"value": 1,
"description": "termFreq=1.0",
"details": []
},
{
"value": 5,
"description": "parameter k1",
"details": []
},
{
"value": 1,
"description": "parameter b",
"details": []
},
{
"value": 3,
"description": "avgFieldLength",
"details": []
},
{
"value": 2,
"description": "fieldLength",
"details": []
}
]
}
]
}
]
},
// others terms result...
]
}
可以清楚地看到每個 query term 會是怎麼在 BM25 中計算出來,包含參數的設定、IDF 值為多少,tfNorm 值為多少等,而 _explain
是 debug 工具,切記要在 production 模式關閉。